package nl.utwente.ewi.hmi.multitouch;

import java.awt.Polygon;
import java.awt.Shape;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import nl.utwente.ewi.hmi.multitouch.event.TouchEvent;
import nl.utwente.ewi.hmi.multitouch.event.TouchJoinEvent;
import nl.utwente.ewi.hmi.multitouch.event.TouchListener;
import nl.utwente.ewi.hmi.multitouch.event.TouchSplitEvent;

/**
 * A Touch object
 * 
 * @author Michiel Hakvoort
 * @version 1.0
 */
public abstract class Touch {

	protected Point2D origin				= null;
	protected Point2D originStart			= null;

	protected Point2D relativeOrigin		= null;
	protected Point2D relativeOriginStart	= null;

	protected Point2D velocity				= null;
	protected Rectangle2D bounds			= null;
	protected final Tangible tangible;
	protected final Date startTime;
	protected Date endTime					= null;
	protected Shape shape					= null;
	protected TouchDevice touchDevice		= null;
	protected Path2D path					= null;
	protected boolean isAmbiguous			= false;

	
	private final Set<TouchListener> listeners;
	
	/**
	 * Create a new Touch created at the given time and for the given {@link Tangible}.
	 * 
	 * @param startTime
	 * @param tangible
	 * @see Tangible
	 */
	public Touch(Date startTime, Tangible tangible) {
		this.startTime = startTime;
		this.tangible = tangible;
		this.listeners = new CopyOnWriteArraySet<TouchListener>();
	}
	
	/**
	 * Get the last known origin of the Touch.
	 * 
	 * @return The last known origin of the Touch
	 */
	public Point2D getOrigin() {
		return this.origin;
	}

	/**
	 * Get the last known origin of the Touch relative to the fixed size of the table input.
	 * The location of this point lies within [0.0 .. 1.0] x [0.0 .. 1.0].
	 * 
	 * @return The origin of the Touch, relative to the table.
	 */
	public Point2D getRelativeOrigin() {
		return this.relativeOrigin;
	}

	/**
	 * Get the first known origin of the Touch. This origin should never change.
	 * 
	 * @return The first known origin of the Touch.
	 */
	public Point2D getOriginStart() {
		return this.originStart;
	}

	/**
	 * Get the first known origin of the Touch relative to the fixed size of the table input.
	 * The location of this point lies within [0.0 .. 1.0] x [0.0 .. 1.0].
	 * 
	 * @return The first known origin of the Touch, relative to the size table.
	 */
	public Point2D getRelativeOriginStart() {
		return this.relativeOriginStart;
	}
	/**
	 * Get the velocity of the Touch. The returned velocity is in moved coordinates per second. 
	 * 
	 * @return The velocity of the Touch.protected Point2D velocity = null;
	 */
	public Point2D getVelocity() {
		return this.velocity;
	}
	/**
	 * Get the time at which the Touch was registered.
	 * 
	 * @return The time at which the Touch was registered.
	 */
	public Date getStartTime() {
		return this.startTime;
	}

	/**
	 * Get the time at which the Touch was released. If the Touch has not been released yet, this will
	 * yield null.
	 * 
	 * @return The time at which the Touch was released, or null if the Touch has not been released yet
	 */
	public Date getEndTime() {
		return this.endTime;
	}

	/**
	 * Return the duration for how long the Touch is active, in milliseconds.
	 * 
	 * @return The duration for how long the Touch is active, in milliseconds.
	 */
	public long getActiveTime() {
		return (new Date().getTime()) - this.getStartTime().getTime();
	}

	/**
	 * Check whether the Touch is still active. If the Touch has been released, this method will return false.
	 * 
	 * @return True if the Touch is still active.
	 */
	public boolean isActive() {
		return this.endTime == null;
	}

	/**
	 * Get the Shape of the Touch.
	 * 
	 * @return The Shape of the Touch.
	 */
	public Shape getShape() {
		return this.shape;
	}

	/**
	 * Get the bounds of the Touch.
	 * 
	 * @return The bounds of the Touch
	 */
	public Rectangle2D getBounds() {
		return this.getShape().getBounds2D();
	}

	/**
	 * Get the {@link Tangible} type of the Touch.
	 * 
	 * @return The {@link Tangible} type of the Touch
	 */
	public Tangible getTangible() {
		return this.tangible;
	}

	/**
	 * If the TouchDevice suffers from possible side effects, 
	 * the Touch will be ambiguous. 
	 * 
	 * @return True when the Touch is a possible side effect.
	 */
	public boolean isAmbiguous() {
		return this.isAmbiguous;
	}

	/**
	 * Get the {@link TouchDevice} which generated the Touch
	 * 
	 * @return The {@link TouchDevice} which generated the Touch
	 */
	public TouchDevice getTouchDevice() {
		return this.touchDevice;
	}
	
	/**
	 * Register a {@linkplain TouchListener} to events of this Touch.
	 * 
	 * @param listener The {@linkplain TouchListener} to register
	 */
	public void addTouchListener(TouchListener listener) {
		this.listeners.add(listener);
	}

	/**
	 * Unregister a {@linkplain TouchListener} from receiving events of this Touch.
	 * 
	 * @param listener The {@linkplain TouchListener} to unregister
	 */
	public void removeTouchListener(TouchListener listener) {
		this.listeners.remove(listener);
	}

	/**
	 * Get the {@link Polygon path} created by the Touch.
	 * 
	 * @return The {@link Polygon path} created by the Touch
	 */
	public Path2D getPath() {
		return this.path;
	}
	
	protected void fireTouchMoved() {
		TouchEvent event = new TouchEvent(this);

		for(TouchListener listener : this.listeners) {
			listener.touchMoved(event);
		}
	}

	protected void fireTouchShapeChanged() {
		TouchEvent event = new TouchEvent(this);

		for(TouchListener listener : this.listeners) {
			listener.touchShapeChanged(event);
		}
	}

	protected void fireTouchReleased() {
		this.endTime = new Date();
		TouchEvent event = new TouchEvent(this);

		for(TouchListener listener : this.listeners) {
			listener.touchReleased(event);
		}
	}

	protected void fireTouchJoined(Set<Touch> joinedTouches, Touch targetTouch) {
		TouchJoinEvent event = new TouchJoinEvent(this, joinedTouches, targetTouch);

		for(TouchListener listener : this.listeners) {
			listener.touchJoined(event);
		}
	}

	protected void fireTouchSplitted(Touch splittedTouch, Set<Touch> targetTouches) {
		TouchSplitEvent event = new TouchSplitEvent(this, splittedTouch, targetTouches);

		for(TouchListener listener : this.listeners) {
			listener.touchSplit(event);
		}
	}

}
